package com.qingzhuge.scheduler.service;

import com.qingzhuge.common.DtoConverter;
import com.qingzhuge.exception.ScheduledException;
import com.qingzhuge.exception.ValidatorException;
import com.qingzhuge.id.SnowflakeId;
import com.qingzhuge.scheduler.dto.request.TaskJobKey;
import com.qingzhuge.scheduler.dto.respone.SysTaskScheduledResponseDto;
import com.qingzhuge.scheduler.entity.SysTaskScheduled;
import com.qingzhuge.scheduler.enums.JobType;
import com.qingzhuge.scheduler.enums.TimeType;
import com.qingzhuge.scheduler.job.BaseJob;
import com.qingzhuge.scheduler.job.StatefulJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobListener;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import static com.qingzhuge.scheduler.entity.SysTaskScheduled.JOB_DATA_MAP_SCHEDULED;

/**
 * @author : zero.xiao
 * @description : 默认的定时任务管理器
 * @date :2019-05-17 11:45
 * @modified :
 */
@Slf4j
@Component
public class SchedulerManager implements InitializingBean {

    @Resource
    private Scheduler scheduler;

    @Resource
    private List<org.quartz.JobListener> jobListeners;
    @Resource
    private List<org.quartz.TriggerListener> triggerListeners;

    @Override
    public void afterPropertiesSet() throws Exception {
        if (jobListeners != null && jobListeners.size() > 0) {
            log.debug("Initing task scheduler[" + scheduler.getSchedulerName() + "] , add listener size ："
                    + jobListeners.size());
            for (JobListener listener : jobListeners) {
                log.debug("Add JobListener : " + listener.getName());
                scheduler.getListenerManager().addJobListener(listener);
            }
            triggerListeners.forEach(l-> {
                log.debug("Add TriggerListener : " + l.getName());
                try {
                    scheduler.getListenerManager().addTriggerListener(l);
                } catch (SchedulerException e) {
                    log.error("add Trigger Listener:{}",e.getMessage());
                }
            });
        }
    }

    List<SysTaskScheduledResponseDto> getAllJobDetail() {
        List<SysTaskScheduledResponseDto> result = new LinkedList<>();
        try {
            GroupMatcher<JobKey> matcher = GroupMatcher.jobGroupContains("");
            Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
            for (JobKey jobKey : jobKeys) {
                JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                for (Trigger trigger : triggers) {
                    JobDataMap jobDataMap = trigger.getJobDataMap();
                    SysTaskScheduled scheduled = (SysTaskScheduled) jobDataMap.get(JOB_DATA_MAP_SCHEDULED);
                    SysTaskScheduledResponseDto scheduledResponseDto = new DtoConverter(){}.doBackward(scheduled, SysTaskScheduledResponseDto.class);
                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                    scheduledResponseDto.setStatus(triggerState);
                    scheduledResponseDto.setFireTime(trigger.getFinalFireTime());
                    //上次执行时间
                    scheduledResponseDto.setPreviousFireTime(trigger.getPreviousFireTime());
                    //下次执行时间
                    scheduledResponseDto.setNextFireTime(trigger.getNextFireTime());
                    String jobClass = jobDetail.getJobClass().getSimpleName();
                    log.debug("job Class:{}",jobClass);
                    result.add(scheduledResponseDto);
                }
            }
        } catch (Exception e) {
            log.error("Try to load All JobDetail cause error : ", e);
        }
        return result;
    }

    /**
     * 新增job
     * @param scheduled 计划任务信息
     */
    void task(SysTaskScheduled scheduled) {
        if (scheduled.isNew()){
            log.debug("添加计划：{}",scheduled);
        }else{
            log.debug("修改计划：{}",scheduled);
        }

        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put(JOB_DATA_MAP_SCHEDULED,scheduled);
        JobDetail jobDetail = getJobDetail(scheduled, jobDataMap);

        String jobName = getJobName(scheduled.getTaskName());
        String jobGroup = getJobGroup(scheduled.getTaskGroup());
        //配置触发器
        TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger()
                .withIdentity(jobName, jobGroup)
                .withDescription(scheduled.getTaskDesc());
        Date startTime = scheduled.getStartTime();
        Date endTime = scheduled.getEndTime();
        //如果开始时间大于当前时间
        if (null != startTime && startTime.getTime() > System.currentTimeMillis()) {
            triggerBuilder.startAt(startTime);
        }else{
            //马上执行
            triggerBuilder.startNow();
        }
        if (null != endTime && endTime.getTime() > System.currentTimeMillis()) {
            triggerBuilder.endAt(endTime);
        }
        ScheduleBuilder scheduleBuilder;
        Integer count = scheduled.getCount();
        //如果有执行次数
        if (null != count && count > 0){
            //间隔时间类型
            TimeType timeType = scheduled.getTimeType();
            //间隔时间
            Integer time = scheduled.getTime();
            scheduleBuilder = getSimpleScheduleBuilder(count, timeType, time);
        }else{
            //规则语法表达示
            String cronExpression = scheduled.getTaskCron();
            if (StringUtils.isEmpty(cronExpression)){
                throw new ValidatorException("时间表达式错误");
            }
            scheduleBuilder = getCronScheduleBuilder(cronExpression);
        }
        triggerBuilder.withSchedule(scheduleBuilder);
        triggerBuilder
                .forJob(jobDetail)
                .usingJobData(jobDataMap);
        //生成触发器
        Trigger trigger = triggerBuilder.build();
        try {
            JobDetail detail = scheduler.getJobDetail(getJobKey(jobName, jobGroup));
            if (detail == null) {
                scheduler.scheduleJob(jobDetail, trigger);
            } else {
                scheduler.addJob(jobDetail, true);
                scheduler.rescheduleJob(getTriggerKey(jobName, jobGroup), trigger);
            }
        } catch (SchedulerException e) {
            throw new ScheduledException(e);
        }
    }

    private String getJobGroup(String jobGroup) {
        if (StringUtils.isEmpty(jobGroup)) {
            jobGroup = "def_job";
        }
        return jobGroup;
    }

    private String getJobName(String jobName) {
        if (StringUtils.isEmpty(jobName)) {
            jobName = String.valueOf(SnowflakeId.next());
        }
        return jobName;
    }

    private JobDetail getJobDetail(SysTaskScheduled scheduled, JobDataMap jobDataMap) {
        JobBuilder jobBuilder;
        //任务类型是否阻塞
        JobType jobType = scheduled.getJobType();
        if (JobType.serial.equals(jobType)) {
            jobBuilder = JobBuilder.newJob(StatefulJob.class);
        }else{
            jobBuilder = JobBuilder.newJob(BaseJob.class);
        }
        String jobName = getJobName(scheduled.getTaskName());
        String jobGroup = getJobGroup(scheduled.getTaskGroup());
        return jobBuilder.withIdentity(jobName, jobGroup)
                .withDescription(scheduled.getTaskDesc())
                .storeDurably(true)
                .usingJobData(jobDataMap)
                .build();
    }

    /**
     *
     * @param cronExpression 时间规则表达式
     */
    private CronScheduleBuilder getCronScheduleBuilder(String cronExpression) {
        return CronScheduleBuilder.cronSchedule(cronExpression);
    }

    /**
     *
     * @param count 执行次
     * @param timeType 时间类型
     * @param time 间隔时间
     */
    private SimpleScheduleBuilder getSimpleScheduleBuilder(Integer count, TimeType timeType, Integer time) {
        if (null == time || time < 1) {
            time = 5;
        }
        SimpleScheduleBuilder simpleScheduleBuilder;
        if (TimeType.SECONDS == timeType){
            //秒
            simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInSeconds(time)
                    .withRepeatCount(count);
        }else if (TimeType.MINUTES == timeType){
            //分
            simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInMinutes(time)
                    .withRepeatCount(count);
        }else if (TimeType.HOURS == timeType){
            //时
            simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInHours(time)
                    .withRepeatCount(count);
        }else{
            //如果没有设置间隔时间类型，仅执行一次
            simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                    .withRepeatCount(1);
        }
        return simpleScheduleBuilder;
    }

    private JobKey getJobKey(String jobName, String jobGroup) {
        return JobKey.jobKey(jobName, jobGroup);
    }

    private TriggerKey getTriggerKey(String jobName, String jobGroup) {
        return TriggerKey.triggerKey(jobName, jobGroup);
    }

    /**
     * 暂停所有触发器
     */
    void pauseAllTrigger() {
        try {
            scheduler.standby();
            log.debug("暂停所有触发器");
        } catch (SchedulerException e) {
            log.error("ScheduledException", e);
            throw new ScheduledException(e);
        }
    }

    /**
     * 启动所有触发器
     */
    void startAllTrigger() {
        try {
            if (scheduler.isInStandbyMode()) {
                scheduler.start();
                log.debug("启动所有触发器");
            }
        } catch (SchedulerException e) {
            log.error("ScheduledException", e);
            throw new ScheduledException(e);
        }
    }

    /** 暂停任务*/
    void stopJob(TaskJobKey taskJobKey) {
        try {
            TriggerKey triggerKey = getTriggerKey(taskJobKey.getTaskName(), taskJobKey.getTaskGroup());
            scheduler.pauseTrigger(triggerKey);
            log.debug("暂停任务:{}",taskJobKey);
        } catch (Exception e) {
            log.error("Try to stop Job cause error : ", e);
            throw new ScheduledException(e);
        }
    }

    /** 启动任务*/
    void resumeJob(TaskJobKey taskJobKey) {
        try {
            TriggerKey triggerKey = getTriggerKey(taskJobKey.getTaskName(), taskJobKey.getTaskGroup());
            scheduler.resumeTrigger(triggerKey);
            log.debug("启动任务:{}",taskJobKey);
        } catch (Exception e) {
            log.error("Try to resume Job cause error : ", e);
            throw new ScheduledException(e);
        }
    }

    /** 执行任务*/
    void runJob(TaskJobKey taskJobKey) {
        try {
            JobKey jobKey = getJobKey(taskJobKey.getTaskName(), taskJobKey.getTaskGroup());
            scheduler.triggerJob(jobKey);
            log.debug("执行任务:{}",taskJobKey);
        } catch (Exception e) {
            log.error("Try to resume Job cause error : ", e);
            throw new ScheduledException(e);
        }
    }

    /**
     * 删除任务
     */
    void delJob(TaskJobKey taskJobKey) {
        try {
            JobKey jobKey = getJobKey(taskJobKey.getTaskName(), taskJobKey.getTaskGroup());
            TriggerKey triggerKey = getTriggerKey(taskJobKey.getTaskName(), taskJobKey.getTaskGroup());
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除任务
            scheduler.deleteJob(jobKey);
            log.debug("删除任务:{}",taskJobKey);
        } catch (Exception e) {
            log.error("Try to resume Job cause error : ", e);
            throw new ScheduledException(e);
        }
    }
}
